#include "uart.h"

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>

static void* pthread_read_event_handle(void *context)
{
    struct uart * puart = NULL;
    int fs_sel;
    puart = (struct uart*)context;
    if(!puart){
        printf("create uart thread error\n");
        return NULL;
    }
    prctl(PR_SET_NAME, "uart");
    while(puart->running_flag){
        fd_set fs_read;
        struct timeval tv;
        struct uart_msg msg;
        FD_ZERO(&fs_read);
        FD_SET(puart->fd, &fs_read);
         
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        fs_sel = select(puart->fd+1, &fs_read, NULL, NULL, &tv);
        if(fs_sel > 0){
            msg.length = read(puart->fd, msg.buffer, MAX_UART_MESSAGE_LENGTH);
            
            if(msg.length > 0){
                puart->op->trigger_notify(puart, UART_EVENT_RECV, &msg);
                printf("data(hex):");
                for(int i = 0; i < msg.length; i++)
                    printf("%02X ", msg.buffer[i]);
                printf("\n");
            }
            wait_wakeup(&puart->wait);
		}else if(fs_sel == 0){
            continue;
		}else{
            perror("info:");
            break;
        }
    }
    return NULL;
}

static int32_t uart_init(struct uart* puart)
{
    int32_t ret = 0;
    struct uart_config* pcfg = &puart->config;

    INIT_LIST_HEAD(&(puart->head));
    lock_init(&(puart->lock));
    lock_init(&(puart->notify_lock));
    wait_init(&(puart->wait));

    pcfg->dev = NULL;
    pcfg->speed = 115200;
    pcfg->databits = 8;
    pcfg->parity = 'N';
    pcfg->stopbits = 1;
    pcfg->stream = 0;

    puart->opened = 0;
    
    return ret;
}

static int32_t uart_release(struct uart* puart)
{
    struct uart_config* pcfg = &puart->config;

    pcfg->dev = NULL;

    lock_destroy((&puart->notify_lock));
    lock_destroy((&puart->lock));
    wait_destroy((&puart->wait));

    return 0;
}

static int32_t uart_open(struct uart* puart)
{
    struct termios new_cfg, old_cfg;
    int speed;
    char dev[64], self[64];
    struct uart_config* pcfg = &puart->config;

    if(pcfg->dev == NULL){
        DBG(DBG_ERR, "no such uart device\n");
        return -1;
    }
    puart->fd = open(pcfg->dev, O_RDWR, 0655);
    if(puart->fd < 0){
        DBG(DBG_ERR, "open %s failed\n", pcfg->dev);
        return -1;
    }
    if(tcgetattr(puart->fd, &old_cfg) != 0){
        DBG(DBG_ERR, "get console attribute failed, err: %s\n", strerror(errno));
        return -1;
    }

    new_cfg = old_cfg;
    cfmakeraw(&new_cfg);
    new_cfg.c_cflag |= CLOCAL | CREAD;
    new_cfg.c_cflag &= ~CSIZE;
    switch(pcfg->speed){
        case 2400:
            speed=B2400;    
            break;
        case 4800:
            speed=B4800;    
            break;
        case 9600:
            speed=B9600;    
            break;
        case 38400:
            speed=B38400;    
            break;
        case 57600:
            speed=B57600;    
            break;
        case 115200:
            speed=B115200;  
            break;
        default :
            speed=B115200; 
            break;
    }

    cfsetispeed(&new_cfg, speed);
    cfsetospeed(&new_cfg, speed);

    switch(pcfg->databits){
        case 7:
            new_cfg.c_cflag|=CS7;   
            break;
        case 8:
            new_cfg.c_cflag|=CS8;   
            break;
        default:
            new_cfg.c_cflag|=CS8;
            break;
    }
    switch(pcfg->parity){
        default:
        case 'n':
        case 'N':
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_iflag &= ~INPCK;
            break;
        case 'o':
        case 'O':
            new_cfg.c_cflag |= (PARODD | PARENB);
            new_cfg.c_iflag |= INPCK;
            break;
        case 'e':
        case 'E':
            new_cfg.c_cflag |= PARENB;
            new_cfg.c_cflag &= ~PARODD;
            new_cfg.c_iflag |= INPCK;
            break;
        case 's':
        case 'S':
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_cflag &= ~CSTOPB;
            break;
    }
    switch(pcfg->stopbits){
        default:
        case 1:
            new_cfg.c_cflag &= ~CSTOPB;
            break;
        case 2:
            new_cfg.c_cflag |= CSTOPB;
            break;
    }
    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[VMIN] = 1;

    tcflush(puart->fd, TCIFLUSH);

    if((tcsetattr(puart->fd, TCSANOW, &new_cfg)) != 0){
        DBG(DBG_ERR, "set console attribute failed, err: %s\n", strerror(errno));
        return -1;
    }
    snprintf(self, sizeof(self), "/proc/self/fd/%d", puart->fd);
    if(readlink(self, dev, sizeof(dev)) > 0){
        DBG(DBG_INFO, "configure %s to bps:%d, bits:%d, partiy:%c, stop:%d\n", 
                      pcfg->dev, pcfg->speed, pcfg->databits, 
                      pcfg->parity, pcfg->stopbits);
    }
    puart->running_flag = 1;
    pthread_create(&puart->pid, NULL, pthread_read_event_handle, puart);

    puart->opened = 1;

    return 0;
}

static int32_t uart_close(struct uart* puart)
{
    if(!puart->opened){
        DBG(DBG_ERR, "uart is closed\n");
        return -1;
    }
    if(puart->running_flag){
        puart->running_flag = 0;
        pthread_join(puart->pid,NULL);
    }
    if(puart->fd > 0){
        close(puart->fd);
    }
    puart->opened = 0;

    return 0;
}

static int32_t uart_write(struct uart* puart, uint8_t* data, uint32_t len)
{
    int slen;

    if(!puart->opened){
        return -1;
    }

    if(puart->fd <= 0){
        puart->opened = 0;
        return -1;
    }

    slen = write(puart->fd, data, len);  
    if (slen != len){
        tcflush(puart->fd, TCOFLUSH);
        DBG(DBG_ERR, "uart send message error\n");
        return -EFAULT;
    }
    return 0;
}

static int32_t uart_write_wait(struct uart* puart, uint8_t* data, uint32_t len)
{
    int slen;

    if(!puart->opened){
        return -1;
    }

    if(puart->fd <= 0){
        puart->opened = 0;
        return -1;
    }
    
    slen = write(puart->fd, data, len);
    if (slen != len){
        tcflush(puart->fd, TCOFLUSH);
        DBG(DBG_ERR, "uart send message error\n");
        return -EFAULT;
    }
    wait_complete(&puart->wait);
    return 0;
}

static int32_t uart_register_notify(struct uart* puart, int32_t event, uart_event_notify notify, void* object)
{
    struct uart_event_action* paction;
    if(!notify || (event <= UART_EVENT_NONE) || (event >= UART_EVENT_MAX)){
        return -EINVAL;
    }
    paction = (struct uart_event_action*)malloc(sizeof(struct uart_event_action));
    if(!paction){
        DBG(DBG_ERR,"malloc error\n");
        return -ENOMEM;
    }
    paction->notify = notify;
    paction->object = object;
    lock(&(puart->notify_lock));
    paction->next = puart->paction[event];
    puart->paction[event] = paction;
    unlock(&(puart->notify_lock));
    return 0;
}

static int32_t uart_unregister_notify(struct uart* puart, int32_t event, void* object)
{
    struct uart_event_action *paction,* ptmp;
    if((event <= UART_EVENT_NONE) || (event >= UART_EVENT_MAX)){
        return -EINVAL;
    }
    lock(&(puart->notify_lock));
    paction = puart->paction[event];
    if(paction){
        if(paction->object == object){
            puart->paction[event] = paction->next;
            free(paction);
        }else{
            while(paction->next){
                if(paction->next->object == object){
                    ptmp = paction->next;
                    paction->next = ptmp->next;
                    free(ptmp);
                    break;
                }
                paction = paction->next;
            }
        }
    }
    unlock(&(puart->notify_lock));

    return 0;
}

static int32_t uart_trigger_notify(struct uart* puart, int32_t event, void* context)
{
    struct uart_event_action* paction;
    if((event <= UART_EVENT_NONE) || (event >= UART_EVENT_MAX)){
        return -EINVAL;
    }
    lock(&(puart->notify_lock));
    paction = puart->paction[event];
    while(paction){
        paction->notify(puart, event, paction->object, context);
        paction = paction->next;
    }
    unlock(&(puart->notify_lock));

    return 0;
}

static struct uart_operation uart_op =
{
    .init = uart_init,
    .release = uart_release,

    .open = uart_open,
    .close = uart_close,

    .write = uart_write,
    .write_wait = uart_write_wait,

    .register_notify = uart_register_notify,
    .unregister_notify = uart_unregister_notify,
    .trigger_notify = uart_trigger_notify,
};

int32_t create_init_uart(struct uart** puart)
{
    int32_t ret;
    struct uart* ptmp;
    (*puart) = (struct uart*)malloc(sizeof(struct uart));
    if(!(*puart)){
        DBG(DBG_ERR,"malloc error\n");
        return -ENOMEM;
    }
    ptmp = *puart;
    memset(ptmp, 0, sizeof(struct uart));
    ptmp->op = &uart_op;
    ret = ptmp->op->init(ptmp);
    if(ret < 0){
        DBG(DBG_ERR,"init error\n");
        release_destroy_uart(ptmp);
        return ret;
    }
    return 0;
}

void release_destroy_uart(struct uart* puart)
{
    if(puart){
        puart->op->release(puart);
        free(puart);
    }
}
